package com.ap.core.jaxb.impl;


import static javax.xml.stream.XMLStreamConstants.END_DOCUMENT;
import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.apache.commons.collections.MapUtils;
import org.eclipse.jetty.util.log.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ap.core.bulkupload.service.impl.ManageMerchantServiceImpl;
import com.ap.core.jaxb.ModelIterator;
import com.ap.core.jaxb.PullModelUnmarshaller;
import com.ap.core.jaxb.SchemaValidationException;
import com.ap.core.jaxb.UnmarshalledData;
import com.ap.core.jaxb.UnmarshallerException;


/**
 * The Class JaxbStaxPullModelUnmarshaller.
 */
public class JaxbStaxPullModelUnmarshaller implements PullModelUnmarshaller {

	/** The input factory. */
	private XMLInputFactory inputFactory;
	
	private static final Logger log = LoggerFactory.getLogger(JaxbStaxPullModelUnmarshaller.class);

	/* (non-Javadoc)
	 * @see com.ap.core.jaxb.PullModelUnmarshaller#unmarshall(java.io.InputStream, java.util.Map, java.util.Map)
	 */
	public ModelIterator unmarshall(final InputStream input, final Map<QName, String> contextMap,
			final Map<QName, Class<?>> typeMapping) throws UnmarshallerException {
		return new ModelIteratorImpl(input, contextMap, typeMapping);
	}

	/**
	 * Iterator implementation.
	 */
	private class ModelIteratorImpl implements ModelIterator {

		/** The xml stream reader. */
		private XMLStreamReader xmlStreamReader;
		
		/** The current. */
		private Object current;
		
		/** The type mapping. */
		private  Map<QName, Class<?>> typeMapping;
		
		/** The unmarshaller map. */
		private Map<QName, Unmarshaller> unmarshallerMap;

		/**
		 * Instantiates a new model iterator impl.
		 *
		 * @param input the input
		 * @param contextMap the context map
		 * @throws UnmarshallerException the unmarshaller exception
		 */
		private ModelIteratorImpl(final InputStream input, final Map<QName, String> contextMap)
				throws UnmarshallerException {
			try {
				unmarshallerMap = new HashMap<QName, Unmarshaller>();

				for (Map.Entry<QName, String> contextMapEntry : contextMap.entrySet()) {
					unmarshallerMap.put(contextMapEntry.getKey(),
									JaxbMarshallerFactory.getUnmarshaller(contextMapEntry.getValue()));
				}
				prepareXmlStreamReader(input);

			} catch (XMLStreamException ex) {
				throw new UnmarshallerException(ex);
			}
		}

		/*
		 * Model Iterator Impl
		 * 
		 * @param input InputStream
		 * 
		 * @param contextMap map of Qname and String
		 * 
		 * @param typeMapping Map of type Mapping
		 */
		/**
		 * Instantiates a new model iterator impl.
		 *
		 * @param input the input
		 * @param contextMap the context map
		 * @param typeMapping the type mapping
		 */
		private ModelIteratorImpl(final InputStream input, final Map<QName, String> contextMap,
				final Map<QName, Class<?>> typeMapping) {
			this(input, contextMap);
			this.typeMapping = typeMapping;
		}

		/*
		 * Prepare XML Stream reader for the given InputStream
		 * 
		 * @param input InputStream
		 * 
		 * @throws FactoryConfigurationError
		 * 
		 * @throws XMLStreamException
		 */
		/**
		 * Prepare xml stream reader.
		 *
		 * @param input the input
		 * @throws FactoryConfigurationError the factory configuration error
		 * @throws XMLStreamException the xML stream exception
		 */
		private void prepareXmlStreamReader(InputStream input)
				throws FactoryConfigurationError, XMLStreamException {
			XMLInputFactory xmlInputFactory = (inputFactory == null) ? XMLInputFactory
					.newInstance() : inputFactory;
			xmlStreamReader = xmlInputFactory.createXMLStreamReader(input);
			xmlStreamReader.nextTag();
		}

		/**
		 * Move the cursor to the end of the next registered element after
		 * Unmarshaling it.
		 *
		 * @return true if next available
		 * @throws UnmarshallerException the unmarshaller exception
		 */
		public boolean hasNext() throws UnmarshallerException {

			try {
				while (xmlStreamReader.getEventType() != END_DOCUMENT && current == null) {
					if (xmlStreamReader.getEventType() == START_ELEMENT) {
						QName name = xmlStreamReader.getName();

						// - NO Type Mapping is available - Unmarshal by
						// "Global root element"
						if (MapUtils.isEmpty(typeMapping)) {
								current = unmarshallerMap.get(name).unmarshal(
										xmlStreamReader);
							// - A Type Mapping is AVAILABLE - Unmarshal by
							// "Declared Type"
						} else if (typeMapping.containsKey(name)) {
							current = unmarshallerMap.get(name).unmarshal(
									xmlStreamReader, typeMapping.get(name));
						}

					}

					if (current == null) {
						xmlStreamReader.next();
					}
				}
			}
			catch (javax.xml.bind.UnmarshalException ex) {
			log.debug("Error while unmarshalling"+ex.getMessage());
				throw new SchemaValidationException(ex.getLinkedException());
			}
			catch (JAXBException ex) {
				log.debug("JaxBException"+ex.getMessage());
				throw new UnmarshallerException(
						"An error occurred while reading the StAX input stream",
						ex);
			} catch (XMLStreamException ex) {
				log.debug("XMLStreamException"+ex.getMessage());
				throw new UnmarshallerException(
						"An error occurred while reading the StAX input stream",
						ex);
			} catch (RuntimeException e) {
				throw new UnmarshallerException(
						"An error occurred while reading the StAX input stream",
						e);

			}

			return current != null;

		}


		/* (non-Javadoc)
		 * @see com.ap.core.jaxb.ModelIterator#next()
		 */
		public Object next() {
			UnmarshalledData data = nextWithMetaData();
			return data.getValue();
		}

		/*
		 * Returns the unmarshalled data
		 * 
		 * @return UnmarshalledData
		 */
		/**
		 * Next with meta data.
		 *
		 * @return the unmarshalled data
		 */
		private UnmarshalledData nextWithMetaData() {
			try {
				if (current == null) {
					throw new IllegalStateException("Reached the end of iterator");
				}
				if (current instanceof JAXBElement<?>) {
					JAXBElement<?> ele = (JAXBElement<?>) current;
					return new UnmarshalledData(ele.getName().toString(),
							ele.getValue());
				} else {
					return new UnmarshalledData(null, current);
				}
			} finally {
				current = null;
			}
		}

	}

	/**
	 * Inject the input factory used to create XML event reader instances.
	 * 
	 * @param inputFactory
	 *            input factory
	 */
	public void setInputFactory(XMLInputFactory inputFactory) {
		this.inputFactory = inputFactory;
	}

}